package ita.class1.group2.supermarket.common.util;

import ita.class1.group2.supermarket.common.annotation.Component;
import ita.class1.group2.supermarket.common.config.DatabaseConfiguration;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.log4j.Logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Database connection manager that manages the connection objects used by the current thread
 *
 * @author Burgess Li
 * @date 2018/07/20
 */
@Component
public class ConnectionManager {

    private static final Logger LOGGER = Logger.getLogger(ConnectionManager.class);
    private static final String DATABASE_CONFIGURATION_FILE_NAME = "/dbconf.properties";

    private DatabaseConfiguration databaseConfiguration = null;
    private BasicDataSource dataSource = null;

    private ConcurrentHashMap<Long, Connection> connectionsMap = null;

    public ConnectionManager() {
        initializeDatabaseConfiguration();
        initializeDBCP();
        this.connectionsMap = new ConcurrentHashMap<Long, Connection>();
    }

    private void initializeDBCP() {
        LOGGER.info("Initialize datasource DBCP");
        dataSource = new BasicDataSource();
        dataSource.setDriverClassName(databaseConfiguration.getDriverClass());
        dataSource.setUrl(databaseConfiguration.getUrl());
        dataSource.setUsername(databaseConfiguration.getUsername());
        dataSource.setPassword(databaseConfiguration.getPassword());
    }

    private void initializeDatabaseConfiguration() {
        LOGGER.info("Initialize database configuration");
        databaseConfiguration = new DatabaseConfiguration(ConnectionManager.class.getResourceAsStream(DATABASE_CONFIGURATION_FILE_NAME));
        LOGGER.info(databaseConfiguration.toString());
    }

    public Connection getConnection() {
        if (this.connectionsMap.containsKey(Thread.currentThread().getId())) {
            return this.connectionsMap.get(Thread.currentThread().getId());
        } else {
            Connection connection = null;
            try {
                connection = dataSource.getConnection();
                this.connectionsMap.put(Thread.currentThread().getId(), connection);
                LOGGER.info("Bind connection: " + connection + " in thread: " + Thread.currentThread().getId());
            } catch (SQLException e) {
                LOGGER.error("Can not get database connection, Message: " + e.toString());
            }
            return connection;
        }
    }

    /**
     * This method is called at the Service layer
     */
    public void closeConnection() {
        Connection connection = this.connectionsMap.get(Thread.currentThread().getId());
        if (connection != null) {
            this.connectionsMap.remove(Thread.currentThread().getId());
            LOGGER.info("Unbind connection: " + connection + ", thread: " + Thread.currentThread().getId());
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        } else {
            LOGGER.warn("Current thread: " + Thread.currentThread().getId() + " not bind connection");
        }
    }

    public void close(ResultSet resultSet, PreparedStatement statement) {
        try {
            if (resultSet != null) resultSet.close();
            if (statement != null) statement.close();
        } catch (SQLException e) {
            LOGGER.error("Can not close result set and statement");
        }
    }

    public void close(PreparedStatement statement) {
        close(null, statement);
    }

}
